/******************************************************************************* * Copyright (c) 2000, 2008 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Randy Hudson <hudsonr@us.ibm.com> * - Fix for bug 19524 - Resizing WorkbenchWindow resizes Views * Cagatay Kavukcuoglu <cagatayk@acm.org> * - Fix for bug 10025 - Resizing views should not use height ratios *******************************************************************************/ package org.eclipse.ui.internal; import java.util.ArrayList; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.util.Geometry; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.ISizeProvider; /** * Implementation of a tree where the node is always a sash * and it always has two children. If a children is removed * the sash, i.e. the node, is removed as well and its other children * placed on its parent. */ public class LayoutTree implements ISizeProvider { /* The parent of this tree or null if it is the root */ LayoutTreeNode parent; /* Any LayoutPart if this is a leaf or a LayoutSashPart if it is a node */ LayoutPart part; // Cached information private int cachedMinimumWidthHint = SWT.DEFAULT; private int cachedMinimumWidth = SWT.DEFAULT; private int cachedMinimumHeightHint = SWT.DEFAULT; private int cachedMinimumHeight = SWT.DEFAULT; private int cachedMaximumWidthHint = SWT.DEFAULT; private int cachedMaximumWidth = SWT.DEFAULT; private int cachedMaximumHeightHint = SWT.DEFAULT; private int cachedMaximumHeight = SWT.DEFAULT; // Cached size flags private boolean sizeFlagsDirty = true; private int widthSizeFlags = 0; private int heightSizeFlags = 0; // Cache statistics. For use in benchmarks and test suites only! public static int minCacheHits; public static int minCacheMisses; public static int maxCacheHits; public static int maxCacheMisses; private boolean forceLayout = true; private Rectangle currentBounds = new Rectangle(0,0,0,0); /** * Initialize this tree with its part. */ public LayoutTree(LayoutPart part) { this.part = part; } /** * Add the relation ship between the children in the list * and returns the left children. */ public LayoutPart computeRelation(ArrayList relations) { return part; } /** * Locates the part that intersects the given point * * @param toFind * @return */ public LayoutPart findPart(Point toFind) { return part; } /** * Dispose all Sashs in this tree */ public void disposeSashes() { } /** * Find a LayoutPart in the tree and return its sub-tree. Returns * null if the child is not found. */ public LayoutTree find(LayoutPart child) { if (part != child) { return null; } return this; } /** * Find the Left,Right,Top and Botton * sashes around this tree and set them * in <code>sashes</code> */ public void findSashes(PartPane.Sashes sashes) { if (getParent() == null) { return; } getParent().findSashes(this, sashes); } /** * Find the part that is in the bottom rigth possition. */ public LayoutPart findBottomRight() { return part; } /** * Find a sash in the tree and return its sub-tree. Returns * null if the sash is not found. */ public LayoutTreeNode findSash(LayoutPartSash sash) { return null; } /** * Return the bounds of this tree which is the rectangle that * contains all Controls in this tree. */ public final Rectangle getBounds() { return Geometry.copy(currentBounds); } /** * Subtracts two integers. If a is INFINITE, this is treated as * positive infinity. * * @param a a positive integer or INFINITE indicating positive infinity * @param b a positive integer (may not be INFINITE) * @return a - b, or INFINITE if a == INFINITE */ public static int subtract(int a, int b) { Assert.isTrue(b >= 0 && b < INFINITE); return add(a, -b); } /** * Adds two positive integers. Treates INFINITE as positive infinity. * * @param a a positive integer * @param b a positive integer * @return a + b, or INFINITE if a or b are positive infinity */ public static int add(int a, int b) { if (a == INFINITE || b == INFINITE) { return INFINITE; } return a + b; } /** * Asserts that toCheck is a positive integer less than INFINITE / 2 or equal * to INFINITE. Many of the methods of this class use positive integers as sizes, * with INFINITE indicating positive infinity. This picks up accidental addition or * subtraction from infinity. * * @param toCheck integer to validate */ public static void assertValidSize(int toCheck) { Assert.isTrue(toCheck >= 0 && (toCheck == INFINITE || toCheck < INFINITE / 2)); } /** * Computes the preferred size for this object. The interpretation of the result depends on the flags returned * by getSizeFlags(). If the caller is looking for a maximum or minimum size, this delegates to computeMinimumSize * or computeMaximumSize in order to benefit from caching optimizations. Otherwise, it delegates to * doComputePreferredSize. Subclasses should overload one of doComputeMinimumSize, doComputeMaximumSize, or * doComputePreferredSize to specialize the return value. * * @see LayoutPart#computePreferredSize(boolean, int, int, int) */ public final int computePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) { assertValidSize(availableParallel); assertValidSize(availablePerpendicular); assertValidSize(preferredParallel); if (!isVisible()) { return 0; } if (availableParallel == 0) { return 0; } if (preferredParallel == 0) { return Math.min(availableParallel, computeMinimumSize(width, availablePerpendicular)); } else if (preferredParallel == INFINITE && availableParallel == INFINITE) { return computeMaximumSize(width, availablePerpendicular); } // Optimization: if this subtree doesn't have any size preferences beyond its minimum and maximum // size, simply return the preferred size if (!hasSizeFlag(width, SWT.FILL)) { return preferredParallel; } int result = doComputePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel); return result; } /** * Returns the size flags for this tree. * * @see org.eclipse.ui.presentations.StackPresentation#getSizeFlags(boolean) * * @param b indicates whether the caller wants the flags for computing widths (=true) or heights (=false) * @return a bitwise combiniation of flags with the same meaning as StackPresentation.getSizeFlags(boolean) */ protected int doGetSizeFlags(boolean width) { return part.getSizeFlags(width); } /** * Subclasses should overload this method instead of computePreferredSize(boolean, int, int, int) * * @see org.eclipse.ui.presentations.StackPresentation#computePreferredSize(boolean, int, int, int) * * @since 3.1 */ protected int doComputePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) { int result = Math.min(availableParallel, part.computePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel)); assertValidSize(result); return result; } /** * Returns the minimum size for this subtree. Equivalent to calling * computePreferredSize(width, INFINITE, availablePerpendicular, 0). * Returns a cached value if possible or defers to doComputeMinimumSize otherwise. * Subclasses should overload doComputeMinimumSize if they want to specialize the * return value. * * @param width true iff computing the minimum width, false iff computing the minimum height * @param availablePerpendicular available space (pixels) perpendicular to the dimension * being computed. This is a height when computing a width, or a width when computing a height. * * @see LayoutPart#computePreferredSize(boolean, int, int, int) */ public final int computeMinimumSize(boolean width, int availablePerpendicular) { assertValidSize(availablePerpendicular); // Optimization: if this subtree has no minimum size, then always return 0 as its // minimum size. if (!hasSizeFlag(width, SWT.MIN)) { return 0; } // If this subtree doesn't contain any wrapping controls (ie: they don't care // about their perpendicular size) then force the perpendicular // size to be INFINITE. This ensures that we will get a cache hit // every time for non-wrapping controls. if (!hasSizeFlag(width, SWT.WRAP)) { availablePerpendicular = INFINITE; } if (width) { // Check if we have a cached width measurement (we can only return a cached // value if we computed it for the same height) if (cachedMinimumWidthHint == availablePerpendicular) { minCacheHits++; return cachedMinimumWidth; } // Recompute the minimum width and store it in the cache minCacheMisses++; int result = doComputeMinimumSize(width, availablePerpendicular); cachedMinimumWidth = result; cachedMinimumWidthHint = availablePerpendicular; return result; } else { // Check if we have a cached height measurement (we can only return a cached // value if we computed it for the same width) if (cachedMinimumHeightHint == availablePerpendicular) { minCacheHits++; return cachedMinimumHeight; } // Recompute the minimum width and store it in the cache minCacheMisses++; int result = doComputeMinimumSize(width, availablePerpendicular); cachedMinimumHeight = result; cachedMinimumHeightHint = availablePerpendicular; return result; } } /** * For use in benchmarks and test suites only. Displays cache utilization statistics for all * LayoutTree instances. * */ public static void printCacheStatistics() { System.out.println("minimize cache " + minCacheHits + " / " + (minCacheHits + minCacheMisses) + " hits " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ minCacheHits * 100 / (minCacheHits + minCacheMisses) + "%"); //$NON-NLS-1$ System.out.println("maximize cache " + maxCacheHits + " / " + (maxCacheHits + maxCacheMisses) + " hits" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ maxCacheHits * 100 / (maxCacheHits + maxCacheMisses) + "%"); //$NON-NLS-1$ } public int doComputeMinimumSize(boolean width, int availablePerpendicular) { int result = doComputePreferredSize(width, INFINITE, availablePerpendicular, 0); assertValidSize(result); return result; } public final int computeMaximumSize(boolean width, int availablePerpendicular) { assertValidSize(availablePerpendicular); // Optimization: if this subtree has no maximum size, then always return INFINITE as its // maximum size. if (!hasSizeFlag(width, SWT.MAX)) { return INFINITE; } // If this subtree doesn't contain any wrapping controls (ie: they don't care // about their perpendicular size) then force the perpendicular // size to be INFINITE. This ensures that we will get a cache hit // every time. if (!hasSizeFlag(width, SWT.WRAP)) { availablePerpendicular = INFINITE; } if (width) { // Check if we have a cached width measurement (we can only return a cached // value if we computed it for the same height) if (cachedMaximumWidthHint == availablePerpendicular) { maxCacheHits++; return cachedMaximumWidth; } maxCacheMisses++; // Recompute the maximum width and store it in the cache int result = doComputeMaximumSize(width, availablePerpendicular); cachedMaximumWidth = result; cachedMaximumWidthHint = availablePerpendicular; return result; } else { // Check if we have a cached height measurement if (cachedMaximumHeightHint == availablePerpendicular) { maxCacheHits++; return cachedMaximumHeight; } maxCacheMisses++; // Recompute the maximum height and store it in the cache int result = doComputeMaximumSize(width, availablePerpendicular); cachedMaximumHeight = result; cachedMaximumHeightHint = availablePerpendicular; return result; } } protected int doComputeMaximumSize(boolean width, int availablePerpendicular) { return doComputePreferredSize(width, INFINITE, availablePerpendicular, INFINITE); } /** * Called to flush any cached information in this tree and its parents. */ public void flushNode() { // Clear cached sizes cachedMinimumWidthHint = SWT.DEFAULT; cachedMinimumWidth = SWT.DEFAULT; cachedMinimumHeightHint = SWT.DEFAULT; cachedMinimumHeight = SWT.DEFAULT; cachedMaximumWidthHint = SWT.DEFAULT; cachedMaximumWidth = SWT.DEFAULT; cachedMaximumHeightHint = SWT.DEFAULT; cachedMaximumHeight = SWT.DEFAULT; // Flags may have changed. Ensure that they are recomputed the next time around sizeFlagsDirty = true; // The next setBounds call should trigger a layout even if set to the same bounds since // one of the children has changed. forceLayout = true; } /** * Flushes all cached information about this node and all of its children. * This should be called if something may have caused all children to become * out of synch with their cached information (for example, if a lot of changes * may have happened without calling flushCache after each change) * */ public void flushChildren() { flushNode(); } /** * Flushes all cached information about this node and all of its ancestors. * This should be called when a single child changes. * */ public final void flushCache() { flushNode(); if (parent != null) { parent.flushCache(); } } public final int getSizeFlags(boolean width) { if (sizeFlagsDirty) { widthSizeFlags = doGetSizeFlags(true); heightSizeFlags = doGetSizeFlags(false); sizeFlagsDirty = false; } return width ? widthSizeFlags : heightSizeFlags; } /** * Returns the parent of this tree or null if it is the root. */ public LayoutTreeNode getParent() { return parent; } /** * Inserts a new child on the tree. The child will be placed beside * the <code>relative</code> child. Returns the new root of the tree. */ public LayoutTree insert(LayoutPart child, boolean left, LayoutPartSash sash, LayoutPart relative) { LayoutTree relativeChild = find(relative); LayoutTreeNode node = new LayoutTreeNode(sash); if (relativeChild == null) { //Did not find the relative part. Insert beside the root. node.setChild(left, child); node.setChild(!left, this); return node; } else { LayoutTreeNode oldParent = relativeChild.getParent(); node.setChild(left, child); node.setChild(!left, relativeChild); if (oldParent == null) { //It was the root. Return a new root. return node; } oldParent.replaceChild(relativeChild, node); return this; } } /** * Returns true if this tree can be compressed and expanded. * @return true if springy */ public boolean isCompressible() { //Added for bug 19524 return part.isCompressible(); } /** * Returns true if this tree has visible parts otherwise returns false. */ public boolean isVisible() { return !(part instanceof PartPlaceholder); } /** * Recompute the ratios in this tree. */ public void recomputeRatio() { } /** * Find a child in the tree and remove it and its parent. * The other child of its parent is placed on the parent's parent. * Returns the new root of the tree. */ public LayoutTree remove(LayoutPart child) { LayoutTree tree = find(child); if (tree == null) { return this; } LayoutTreeNode oldParent = tree.getParent(); if (oldParent == null) { //It was the root and the only child of this tree return null; } if (oldParent.getParent() == null) { return oldParent.remove(tree); } oldParent.remove(tree); return this; } /** * Sets the bounds of this node. If the bounds have changed or any children have * changed then the children will be recursively layed out. This implementation * filters out redundant calls and delegates to doSetBounds to layout the children. * Subclasses should overload doSetBounds to lay out their children. * * @param bounds new bounds of the tree */ public final void setBounds(Rectangle bounds) { if (!bounds.equals(currentBounds) || forceLayout) { currentBounds = Geometry.copy(bounds); doSetBounds(currentBounds); forceLayout = false; } } /** * Resize the parts on this tree to fit in <code>bounds</code>. */ protected void doSetBounds(Rectangle bounds) { part.setBounds(bounds); } /** * Set the parent of this tree. */ void setParent(LayoutTreeNode parent) { this.parent = parent; } /** * Set the part of this leaf */ void setPart(LayoutPart part) { this.part = part; flushCache(); } /** * Returns a string representation of this object. */ public String toString() { return "(" + part.toString() + ")";//$NON-NLS-2$//$NON-NLS-1$ } /** * Creates SWT controls owned by the LayoutTree (ie: the sashes). Does not affect the * LayoutParts that are being arranged by the LayoutTree. * * @param parent */ public void createControl(Composite parent) { } /** * Writes a description of the layout to the given string buffer. * This is used for drag-drop test suites to determine if two layouts are the * same. Like a hash code, the description should compare as equal iff the * layouts are the same. However, it should be user-readable in order to * help debug failed tests. Although these are english readable strings, * they should not be translated or equality tests will fail. * <p> * This is only intended for use by test suites. * </p> * * @param buf */ public void describeLayout(StringBuffer buf) { part.describeLayout(buf); } /** * This is a shorthand method that checks if the tree contains the * given size flag. For example, hasSizeFlag(false, SWT.MIN) returns * true iff the receiver enforces a minimum height, or * hasSizeFlag(true, SWT.WRAP) returns true iff the receiver needs to * know its height when computing its preferred width. * * @param vertical * @return */ public final boolean hasSizeFlag(boolean width, int flag) { return (getSizeFlags(width) & flag) != 0; } }